#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>

int procura_tab_l_v(const char *linha);

/*GLOBAIS*/

/*Estrutura */
typedef struct
{
    char nome[30];
    int valor;
}Labels_Variaveis;

union Registrador
{
    short int x;
    char l_h[2];
};
union Registrador ax;
union Registrador bx;

/* numeor de registradores e instrucao utilizados */
const int numero_registradores = 38;
const int numero_instrucoes = 35;

/* delimitadores para cortar as linhas do codigo */
char delimitadores[] = ":;,";

char nomeArquivo;
char nome_arquivo_montador;
char nome_arquivo_asm;
FILE* arquivo_asm;
FILE* arquivo_txt;

/* Tabela de mnomonicos (instrucoes) e registradores */
char tabela_registradores[38][20] = {"indf","tmr0","pcl","status","fsr","porta","portb","pclath","intcon","pir1","tmr1l","tmr1h","t1con"," tmr2","t2con","ccpr1","ccpr1l","ccpr1h","ccp1con","rcsta","txreg","rcreg","cmcon","option_reg","trisa","trisb","pie1","pcon","pr2","txsta","spbrg","eedata","eeadr","eecon1","eecon2","vrcon",};
char tabela_instrucoes[35][10] ={"addwf","andwf","clrf","clrw","comf","decf","decfsz","incf","incfsz","iorwf","movf","movwf","nop","rlf","rrf","subwf","swapf","xorwf","bcf","bsf","btfsc","btfss","addlw","andlw","call","clrwdt","goto","iorlw","movlw","retfie","retlw","return","sleep","sublw","xorlw"};

/* tabela dde valores dos labels e variaveis (fixo me 1000) */
Labels_Variaveis tabela_l_v[1000];

/* contador para posicao valida na tabela de labels e variaveis*/
int posicao_tab_l_v;

/*contador de posicao do opcode */
int pos;

/*
 * ansi c "itoa"
 */
void strreverse(char* begin, char* end){
    char aux;
    while(end>begin)
        aux=*end, *end--=*begin, *begin++=aux;
}

void itoa(int value, char* str, int base){
    static char num[] = "0123456789abcdefghijklmonpqrstuvwxyz";
    char* wstr=str;
    int sign;

    //valida base
    if(base<2 || base>35){
        *wstr='\0';
        return;
    }

    //Take care of sign
    if((sign=value) < 0)
        value = -value;

    //Conversion; Number is reversed.
    do *wstr++= num[value%base];while(value/=base);
    if(sign<0)
        *wstr++='-';

    *wstr='\0';

    //Reverse string
    strreverse(str,wstr-1);
}

/*Como nao existe a funcao strlwr() no mundo ANSIIC, escrevemos a funcao que
 * devolve tudo em minusculo*/
char *strlwr(const char *str)
{
    register int count;
    char *str_lwr;

    for(count=0; str[count]; count++)

    str_lwr = (char *)malloc(sizeof(char)*(count+1));

    while(*str){
        *str_lwr = tolower(*str);
        str_lwr++;
        str++;
    }
    *str_lwr = ' ';

    return(str_lwr-count);
}
/*funcao que da mensagem de erro e finaliza o programa */
void erro(const char *erro)
{
    printf("ERRO");
    system("pause");
    exit(1);
}
/* Funcao para verificar se é label ou variavel e encontrar o seu lugar na
 * tabela */
int procura_tab_l_v(const char *linha)
{
    int i;
    if(linha != NULL)
    {
        /*procura na tabela se eh label ou variavel */
        for(i=0; i<1000; i++){
            if(strcmp(linha, tabela_l_v[i].nome) == 0)
                return i;
        }
    }
    return -1;
}

bool e_registrador(char *linha)
{
    int i;
    /*procura na tabela de registradores */
    for(i = 0; i < numero_registradores; i++)
    {
        if(linha == tabela_registradores[i])
            return true;
    }
    return false;
}

/*funcao para ver se eh memoria */
bool e_variavel(char *linha)
{
    if(linha[0] == '@')
        return false;
    else
    {
        int i = procura_tab_l_v(linha);
        if(i != -1)
            return true;
    }
    return false;
}

/*funcao para verificar o modo de enderecamento */
int modo_enderecamento(char *operando1, char *operando2)
{
    /* se dois operadores forem nulos */
    if(operando1 == NULL && operando2 == NULL)
        return 0;


    if(e_registrador(operando1) && operando2 == NULL)
        return 5;

    if(e_registrador(operando1) && e_variavel(operando2))
        return 3;
    if(e_variavel(operando1) && e_registrador(operando2))
        return 4;

    return 0;
}

/*Funcao que verifica se eh uma instrucao */
int e_instrucao(const char *linha)
{
    int i;
    /*variavel para armazenar temporariamente a linha(fixo em 50) */
    char aux[5];
    strcpy(aux, linha);
    char *token = strtok(aux, delimitadores);

    /*procurar na tabela de instrucao se a primeira palavra da linha (token) é
     * uma instrucao*/
    for(i = 0; i < numero_instrucoes; i++){
        if(token == tabela_instrucoes[i])
            return i;
    }

    return -1;
}

/* Funcao para adicionar na tabela de labels e variaveis */
void adiciona_tab_l_v(const char *linha, const int valor)
{
    int aux = procura_tab_l_v(linha);
    /*se nao encontra na tabela de labels e variaveis */
    if(aux == -1)
    {
        strcpy(tabela_l_v[posicao_tab_l_v].nome, linha);
        tabela_l_v[posicao_tab_l_v].valor = valor;
        posicao_tab_l_v++;
    }

    /* senao, encontrou entao atualiza valor*/
    else
        tabela_l_v[aux].valor = valor;
}

/* funcao para retornar o tamanho de uma instrucao */
int tamanho_instrucao(char* instrucao)
{
    char *operador, *operando1, *operando2;
    operador = strtok(instrucao, delimitadores);
    operando1 = strtok(NULL, delimitadores);
    operando2 = strtok(NULL, delimitadores);

    /* se for maria   tem tamnho fixo */
    if(operador[0] == 'm')
    {
        adiciona_tab_l_v(operando1, -1);
        return 2;
    }

    else{
        /* Todos tem um operando, se qualquer outro tiver apenas um entrao da
         * erro por falta de operando */
        int a = e_instrucao(operador);
        if(a != -1)
        {
            if((tabela_instrucoes[a] !="movlw") &&(tabela_instrucoes[a] != "movwf") &&(tabela_instrucoes[a] != "clrf") && (tabela_instrucoes[a] != "goto"))
            {
                if(operando2 == NULL)
                    erro("Operando(s) invalido(s)");
            }
        }

        /*dependo do modo de enderecamento */
        switch(modo_enderecamento(operando1, operando2))
        {
            case 0:
                erro("operando(s) invalido(s)");
            case 1:
                return 3;
            case 2:
                return 3;
            case 3:
                return 3;
            case 4:
                return 3;
            case 5:
                return 2;

        }
    }
    return 0;
}


/* funcao que grava um numero no arquivo .txt*/
void grava(const int val)
{
    fprintf(arquivo_txt, "%d\n", val);
}

/*funcao que verifica qual eh a instrucao e retorna seu sodigo*/
int codigo_instrucao(const char *instrucao, int modo)
{
    if(strcmp(instrucao, "movlw") == 0)
    {
        switch(modo)
        {
            case 1:
                return 10000;
        }
    }

    if(strcmp(instrucao, "movwf") == 0)
    {
        switch(modo)
        {
            case 1:
                return 20000;
        }
    }
}

/*funcao que verifica qual eh o Registrador e retorna a posicao dele no vetor
 * de registradores */
int qual_registrador(const char *s)
{
    if(strcmp(s, "b") == 0)
        return 0;
    if(strcmp(s, "cmcon") == 0)
        return 1;
    if(strcmp(s, "porta") == 0)
        return 2;
    if(strcmp(s, "portb") == 0)
        return 3;
    if(strcmp(s, "trisa") == 0)
        return 4;
    if(strcmp(s, "trisb") == 0)
        return 5;
    if(strcmp(s, "option_reg") == 0)
        return 6;
    if(strcmp(s, "intcon") == 0)
        return 7;

    return 0;

}
/* funcao que trata a linha e a salva no arquivo*/
bool grava_instrucao(char *linha)
{
    char del[] = " :;,\n";
    char * operador, *operando1, *operando2;
    int modo;
    int i, j, b, b1, codigo;

    /*separa a linha nas 3 partes que importam */
    operador = strtok(linha, del);
    operando1 = strtok(NULL, del);
    operando2 = strtok(NULL, del);

    /*se o operador comecar com i */
    if(operador[0] = 'j')
    {
        i = procura_tab_l_v(operando1);
        if(tabela_l_v[i].valor == -1)
            erro("label nao encontrada");

        /*escrev no arquivo o codigo para essa instrucao e o seu valor*/
        grava(codigo_instrucao(operador,0)); //passo 0 pois em salto nao ha modo de enderecamento, logo nao importa*/
        grava((int)tabela_l_v[i].valor);
    }
    else{
        /* se for numero */
        if((operando2 != NULL) && !(e_variavel(operando2)) && !(e_registrador(operando2)))
        {
            b = atoi(operando2);
        }
        if((operando2 != NULL) && (e_variavel(operando1)))
        {
            j = procura_tab_l_v(operando1);
            b1 = tabela_l_v[j].valor;
        }
        else if((operando2 != NULL) && (e_variavel(operando2)))
        {
            j = procura_tab_l_v(operando2);
            b1 = tabela_l_v[i].valor;
        }
        /*salvo o modo de enderecamento */
        modo = modo_enderecamento(operando1, operando2);

        /*salvo o codigo da instrucao */
        codigo = codigo_instrucao(operador,modo);

        switch(modo)
        {
            case 0:
                erro("Operando(s) invalido(s)");
            case 1:
                grava(codigo);
                grava(qual_registrador(operando1));
                grava(qual_registrador(operando2));
        }
    }
    return 0;

}

/* Funcao que lê o arquivo fonte.*/
int preencher_tabelas(const char* nomeArquivo)
{
    //char* noomeArquivo;
    pos = 0;
    char* token;
    char line[100], Line[100];
    int aux, numero_de_linhas = 0; //apenas para controle, nao tem funcionalidade

    FILE* arquivo = fopen(nomeArquivo, "r");
    if(arquivo == NULL){
        printf(">> Nao foi possivel abrir o arquivo fonte");
        //system("pause");
        exit(1);
    }
    rewind(arquivo);
    while(!feof(arquivo)){
        fgets(line, 100, arquivo);
        //strlwr(line);
        strcpy(Line, line);
        token = strtok(line, " \t\n");
        printf("%s", Line); //teste

        if(token == NULL) //pula linha em  branco
            continue;

        else if(token[0]=';')//pula comentario no primeiro termo
            continue;

        /* Se nao for comentario continua, se for apenas ignoras a linha */
        else
        {
            /* Se for fim do programa fina a funcao */
            if(strcmp(strtok(line, ";"), "END") == 0){
                fclose(arquivo);
                return 0;
            }

            /* se for instrucao, incrementa contador de posicao,de acordo com o
             * tamnho da instrucao no opcode */
            else if(e_instrucao(line)>-1)
                pos += tamanho_instrucao(line);

            /*senao, so pode ser declaracao de variavel */
            else{
                token = strtok(line, delimitadores);
                if(token != NULL){
                    /* se nao encontra na tabela, adciona. se encontrou erro de
                     * redeclaracao */
                    if(procura_tab_l_v(token) == -1){
                        strcpy(tabela_l_v[posicao_tab_l_v].nome, token);
                        posicao_tab_l_v++;
                    }
                    else
                        erro("variavel redeclarada");

                    /*pego proximo corte da linha */
                    token = strtok(NULL, delimitadores);

                    /*se nao tiver nada de erro */
                    if(token == NULL)
                        erro("variavel declarada de forma errada");

		    /*verifica se é a declaração de uma word (dw)*/
		    else if(strcmp(token, "dw") == 0)
		    {
			/*pego próximo corte*/
			token = strtok(NULL, delimitadores);
			if(strcmp(token,"?") != 0)
			{
			    /*se o numero a ser indexado na variável é maior que uma word, da erro*/
			    if(atoi(token) > 65535) //(2^16 - 1, dois bytes)
				erro("tentativa de indexar um numero fora dos limites de dois byte (65535)");
			}
			aux = posicao_tab_l_v - 1; //pq já incrementei lá em cima e quero a posição onde inseri o nome
			tabela_l_v[aux].valor = pos;
			pos += 2; //incremento a variável de opcodes em 2, pq word são dois bytes
		    }
		    /*no pior dos casos, se não foi nada reconhecível até agora, erro*/
		    else
			erro("argumento invalido");
                }
            }
        }
        numero_de_linhas++;
    }
    fclose(arquivo);
    return 0;
}

bool gerar_arquivo(const char* nome_arquivo_montador, const char *nome_arquivo_asm)
{
    //char nome_arquivo_montador;
    /*abre o arquivo .txt onde será gravado a montagem */
    FILE* arquivo_txt = fopen(nome_arquivo_montador, "w");
    if(arquivo_txt == NULL)
        erro("\nArquivo Invalido");

    /*abre arquivo .asm de entrada */
    FILE* arquivo_asm = fopen(nome_arquivo_asm, "r");
    if(arquivo_asm =NULL)
        erro("\nArquivo Invalido");

    char *token;
    int n = 0, a, l;
    char linha[100], b[17] = "", c[9] = " ", d[9] = " ";
    char Line[100];

    rewind(arquivo_asm);
    /*enquanto nao for fim arquivo */
    while(!feof(arquivo_asm))
    {
        /*pego linha por linha */
        fgets(linha, 100, arquivo_asm);
        strlwr(linha);
        strcpy(Line, linha);
        token = strtok(linha, "\t\n");
        printf("%s", linha);

        if(strcmp(strtok(linha, ";"), "end") == 0)
        {
            fclose(arquivo_asm);
            return true;
        }

        /*se instrucao, increnta contador de posicao de acordo com o tamanho da
         * instrucao*/
       /* if(e_instrucao(linha) > -1)
            grava_instrucao(linha);*/

        /*se for declaracao de variavel grava o valor dela */
        /*else{
            token = strtok(linha, delimitadores);*/
            /*if(strcmp(token, "b") == 0) || (strcmp(token, "movwf") == 0) || (strcmp(token, "goto") == 0)
            {
                erro("numeacao de variavel utilizado uma palavra reservada ao sistema");
            }*/

          /*  if(procura_tab_l_v(token) != -1){
                token = strtok(NULL, delimitadores);
                if(strcmp(token, "cmcon") == 0)
                {
                    token = strtok(NULL, delimitadores);
                    grava(atoi(token));
                }
                else if(strcmp(token, "trisa") == 0)
                {
                    token = strtok(NULL, delimitadores);
                    //convertendo para binario
                    a = atoi(token);
                    //itoa(a,b,2);
                }
            }
        }*/

    }
    fclose(arquivo_asm);
    fclose(arquivo_txt);
    return true;
}

int montador()
{
    int opcao;
    printf("\n##\tMENU\t##");
    printf("\n##\t \t##");
    printf("\n----------------------------------------------------\n");
    printf("1 - Carregar o arquivo\n");
    printf("2 - Gerar arquivo binario\n");
    printf("3 - sair\n");
    scanf("%d", &opcao);
    switch(opcao){
        case 1:
            printf("\nOpcao de ler arquivo!");
            printf("\nEntre com o nome do arquivo: ");
            scanf("%s", &nome_arquivo_asm);
            printf("\nNome do arquivo a ser lido eh: %s\n", &nome_arquivo_asm);
            preencher_tabelas(&nome_arquivo_asm);
            break;
        case 2:
            printf("Qual o nome do arquivo que voce gostaria de grava .txt: ");
            scanf("%s", &nome_arquivo_montador);
            printf("\nEntre com o nome do arquivo .asm: ");
            scanf("%s", &nome_arquivo_asm);
            gerar_arquivo(&nome_arquivo_montador, &nome_arquivo_asm);
            printf("\nArquivo gerado com sucesso!");
            break;
        case 3:
            printf("Finalizando....");
            exit(1);
            break;
        default:
            printf("Opcao invalida!");
    }
    return 0;
}

int main()
{
    while(1)
        montador();
    return 0;
}
